package particle_simulator_chem;
 
import java.util.ArrayList;
 
import org.eclipse.swt.graphics.Color;
 
import chemistry.Element;
import chemistry.ElementList;
import chemistry.FormulaParser;
 
public class Boid {
    private Point3D location;
    private double xVel;
    private double yVel;
    private double zVel;
    private Color color;
    private String text;
    private String raw_formula;
    private ArrayList<Element> formula;
    private boolean compound;
 
//////////////////////////////////Initialization////////////////////////////////////
     
    //2D constructor
    public Boid(double x, double y, double xVel, double yVel, Color color, String text, String formula, ElementList elements) {
        this.location = new Point3D(x, y);
        this.xVel = xVel;
        this.yVel = yVel;
        this.color = color;
        this.setText(text); 
        this.setRawFormula(formula);
        this.formula = FormulaParser.getElements(formula, elements);
    }
     
    //3D constructor
    public Boid(double x, double y, double z, double xVel, double yVel, double zVel, Color color, String text, String formula, ElementList elements) {
        this.location = new Point3D(x, y, z);
        this.xVel = xVel;
        this.yVel = yVel;
        this.zVel = zVel;
        this.color = color;
        this.setText(text); 
        this.setRawFormula(formula);
        this.formula = FormulaParser.getElements(formula, elements);
    }
 
//////////////////////////////////Library Functions/////////////////////////////////
     
    /**
     * Calculates the distance between two points in 3-space using the pythagorean theorem.
     * 
     * Formula used for calculation:
     *         d = sqrt(((x1 - x2) ^ 2) + ((y1 - y2) ^ 2) + ((z1 - z2) ^ 2))
     * 
     * @param x1
     * @param y1
     * @param z1
     * @param x2
     * @param y2
     * @param z2
     * @return distance
     */
    public static double pTheorem3D(double x1, double y1, double z1, double x2, double y2, double z2) {
        return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) + Math.pow(z1 - z2, 2));
    }
 
    /**
     * Calculates the horizontal angle between two points in 3-space.
     * 
     * Formula used for calculation:
     *         hAngle = atan2(y2 - y1, x2 - x1)  
     * 
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @return hAngle
     */
    public static double hAngle(double x1, double y1, double x2, double y2) {
        return Math.atan2(y2 - y1, x2 - x1);
    }
     
    /**
     * Calculates the vertical angle between two points in 3-space.
     * 
     * Formula used for calculation:
     *         vAngle = atan2(y2 - y1, sqrt((x1 - x2) ^ 2 + (z1 - z2) ^ 2))  
     * 
     * @param x1
     * @param y1
     * @param z1
     * @param x2
     * @param y2
     * @param z2
     * @return vAngle
     */
    public static double vAngle(double x1, double y1, double z1, double x2, double y2, double z2) {
        return Math.atan2(y2 - y1, Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(z1 - z2, 2)));
    }
     
    /**
     * Calculates the point on the given 2-sphere with a horizontal and a vertical angle.
     * 
     * Formulae used for calculation:
     *         x = sphereX + sin(hAngle) * sin(vAngle) * sphereRadius
     *         y = sphereY + sin(vAngle) * sphereRadius;
     *         z = sphereZ + cos(hAngle) * sin(vAngle) * sphereRadius
     *
     * Sphere x, y, and z correspond to the center of the provided 2-sphere.
     * 
     * @param hAngle
     * @param vAngle
     * @param sphereX
     * @param sphereY
     * @param sphereZ
     * @param sphereRadius
     * @return point
     */
    public static Point3D pointOnSphere(double hAngle, double vAngle, double sphereX, double sphereY, double sphereZ, double sphereRadius) {
        return new Point3D(sphereX + Math.sin(hAngle) * Math.sin(vAngle) * sphereRadius, 
                sphereY + Math.sin(vAngle) * sphereRadius,
                sphereZ + Math.cos(hAngle) * Math.sin(vAngle) * sphereRadius);
    }
     
    /**
     * Finds the horizontal and vertical angles between this boid and the provided point in 3-space using hAngle(...) and vAngle(...)
     * 
     * @param otherPoint
     * @return angles
     */
    public double[] angleTo(Point3D otherPoint) {
        double[] angles = new double[2]; //Dimension - 1 angles are needed
        angles[0] = hAngle(this.getX(), this.getY(), otherPoint.getX(), otherPoint.getY());
        angles[1] = vAngle(this.getX(), this.getY(), this.getZ(), otherPoint.getX(), otherPoint.getY(), otherPoint.getZ());
        return angles;
    }
     
    /**
     * Finds the distance between two points in 3-space using pTheorem3D(...)
     * 
     * @param otherPoint
     * @return distance
     */
    public double distanceTo(Point3D otherPoint) {
        return pTheorem3D(this.getX(), this.getY(), this.getZ(), otherPoint.getX(), otherPoint.getY(), otherPoint.getZ());
    }
 
//////////////////////////////////Setters and Getters///////////////////////////////
     
    public double getX() {
        return this.location.getX();
    }
     
    public double getY() {
        return this.location.getY();
    }
     
    public double getZ() {
        return this.location.getZ();
    }
     
    public Point3D getLocation() {
        return this.location;
    }
     
    public Color getColor() {
        return this.color;
    }
     
    public double getXVel() {
        return this.xVel;
    }
     
    public double getYVel() {
        return this.yVel;
    }
     
    public void setXVel(double xVel) {
        this.xVel = xVel;
    }
     
    public void setYVel(double yVel) {
        this.yVel = yVel;
    }
     
    public double getUpdateRadius() {
        return (double) this.getAttrRadius();
    }
     
    public void setText(String text) {
        this.text = text;
    }
     
    public String getText() {
        return text;
    }
     
    public ArrayList<Element> getFormula() {
        return this.formula;
    }
     
    public int getAttrRadius() {
        int radSum = 0;
        for (Element e: this.formula) {
            radSum += e.getAtomicRadius();
        }
        return radSum / 25 + 8;
    }
     
    public double getAttrStrength() {
        int massSum = 0;
        for (Element e: this.formula) {
            massSum += e.getMass();
        }
        return massSum / 3300.0;
    }
     
    public double getRepulsionStrength() {
        int massSum = 0;
        for (Element e: this.formula) {
            massSum += e.getMass();
        }
        return massSum / 920.0;
    }
     
    public int getRepulsionRadius() {
        int attr = this.getAttrRadius();
        return (int) (attr - .35 * attr);
    }
     
    public double getReactionRadius() {
        int chargeSum = 0;
        for (Element e: this.formula) {
            chargeSum += e.getCharge();
        }
        if (chargeSum >= 0 && this.formula.get(0).getElectronegativity() != 0) {
            double eSum = 0;
            for (Element e: this.formula) {
                eSum += e.getIonizationEnergy();
            }
            eSum /= this.formula.size();
            return eSum + this.getRepulsionRadius();
        } else {
            double eSum = 0;
            for (Element e: this.formula) {
                eSum += e.getElectronegativity();
            }
            eSum /= this.formula.size();
            return eSum;
        }
    }
     
//////////////////////////////////Updaters//////////////////////////////////////////
     
    public void updatePosition() {
        this.location.setX(this.getX() + this.xVel); //identical to +=, modified for private fields
        this.location.setY(this.getY() + this.yVel);
        if ((Double) this.zVel != null) {
            this.location.setZ(this.getZ() + this.zVel);
        }     
    }
     
    public Boid updateNeighborVelocities(ArrayList<Boid> neighbors) {
        Boid react = this;
        for(Boid boid : neighbors) { //for each boid in neighbors
            double distance = this.distanceTo(boid.getLocation());
            double[] angle = this.angleTo(boid.getLocation());
            if (distance <= this.getReactionRadius()) {
                react = boid;
            }
            if(distance <= this.getAttrRadius()) {
                double strength = (this.getAttrRadius() / (distance + 0.01)) * this.getAttrStrength(); //Adding a small number to distance prevents a divide by zero error (which would occur if two particles occupied the same position)
                boid.xVel -= Math.cos(angle[0]) * strength * (75.0 / FormulaParser.getMass(boid.formula));
                boid.yVel -= Math.sin(angle[0]) * strength * (75.0 / FormulaParser.getMass(boid.formula));
                if ((Double) this.zVel != null) {
                    boid.zVel -= Math.tan(angle[1]) * strength * (75.0 / FormulaParser.getMass(boid.formula));
                }     
            }
            if(distance <= this.getRepulsionRadius()) {
                double strength = (this.getRepulsionRadius() / (distance + 0.01)) * this.getRepulsionStrength();
                boid.xVel += Math.cos(angle[0]) * strength * (75.0 / FormulaParser.getMass(boid.formula));
                boid.yVel += Math.sin(angle[0]) * strength * (75.0 / FormulaParser.getMass(boid.formula));
                if ((Double) this.zVel != null) {
                    boid.zVel += Math.tan(angle[1]) * strength * (75.0 / FormulaParser.getMass(boid.formula));
                }     
            }
        }
        return react;
    }
 
    public void setRawFormula(String raw_formula) {
        this.raw_formula = raw_formula;
    }
     
    public String getRawFormula() {
        return raw_formula;
    }
 
    public void setCompound(boolean compound) {
        this.compound = compound;
    }
 
    public boolean isCompound() {
        return compound;
    }
 
    public void setZVel(double zVel) {
        this.zVel = zVel;
    }
 
    public double getZVel() {
        return zVel;
    }
}

